package com.ditri.distrident.common.utils;

import com.ditri.distrident.common.constant.CommonConst;
import com.ditri.distrident.common.constant.ErrorCodeEnum;
import com.ditri.distrident.common.entity.Result;
import com.ditri.distrident.common.pojo.Mail;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;

/**
 * 发送邮件
 *
 * @author: edxuanlen
 * @date: 2020-08-04 14:08
 * @version: 1.0
 **/

@Slf4j
public class SendMailUtil {
	/**
	 * 等待发送的邮件队列
	 */
	private static final BlockingQueue<Mail> MAIL_QUEUE;

	/**
	 * 上一次更新发送计数的时间
	 */
	private static final AtomicLong LAST_UPDATE_SEND_COUNT_TIME = new AtomicLong(CommonUtil.getCalendar(1970));

	/**
	 * 今天已发送邮件计数
	 */
	private static final AtomicInteger SENT_MAIL_TODAY = new AtomicInteger(0);

	/**
	 * 邮箱发送格式设置
	 */
	private static final Properties PROPERTIES = new Properties();

	private static final Random RANDOM = new Random();

	@Resource
	private RedisTemplate<String, String> redisTemplate;

	private static Session session;

	public SendMailUtil() {
		session = Session.getInstance(PROPERTIES);
		session.setDebug(true);
	}

	static {

		MAIL_QUEUE = new ArrayBlockingQueue<>(100);
		PROPERTIES.setProperty(CommonConst.Mail.AUTH.getKey(), CommonConst.Mail.AUTH.getValue());
		PROPERTIES.setProperty(CommonConst.Mail.PROTOCOL.getKey(), CommonConst.Mail.PROTOCOL.getValue());
		PROPERTIES.setProperty(CommonConst.Mail.HOST.getKey(), CommonConst.Mail.HOST.getValue());
	}

	public Result sendAuthCode(String recipient) {
        // 判断账号是否是邮箱类型
        Pattern pattern = CommonConst.Patterns.MAIL_PATTERN;
        boolean matchSuccess = pattern.matcher(recipient).matches();
        if (!matchSuccess) {
            return ErrorCodeEnum.MAIL_ADDRESS_FORMAT_ERROR.getResult();
        }

		// check 恶意发送,频繁发送邮件
		{
			String redisKey = CommonConst.Redis.AUTH_CODE_SEND_TIME_PREFIX + recipient;
			String numberOfSendingWithOneHour = redisTemplate.opsForValue().get(redisKey);
			int numberOfSendingWithOneHourInt;
			// 有记录
			if (!CommonUtil.isEmpty(numberOfSendingWithOneHour)) {
				assert numberOfSendingWithOneHour != null;
				numberOfSendingWithOneHourInt = Integer.parseInt(numberOfSendingWithOneHour);
				if (numberOfSendingWithOneHourInt == CommonConst.Mail.MAX_NUMBER_OF_SENDING_WITH_ONE_HOUR) {
					return ErrorCodeEnum.SEND_MAIL_FREQUENTLY.getResult();
				} else {
					// 没有记录获取过期时间并设置
					Long overdueTime = redisTemplate.opsForValue().getOperations().getExpire(redisKey);
					// 没查到过期时间，则重新生成过期时间为一小时
					if (overdueTime == null) {
						overdueTime = CommonConst.Time.SECONDS_OF_ONE_HOUR;
					}
					redisTemplate.opsForValue().set(redisKey, String.valueOf(numberOfSendingWithOneHourInt + 1), overdueTime, TimeUnit.SECONDS);
				}
			} else {
				// 没有记录则新增
				redisTemplate.opsForValue().set(redisKey, "1", CommonConst.Time.SECONDS_OF_ONE_HOUR, TimeUnit.SECONDS);
			}
		}

		// 判断当天已发次数
		{
			// 检查上次更新次数时间
			long lastUpdateSendCountTime = LAST_UPDATE_SEND_COUNT_TIME.get();
			long newUpdateSendCountTime = CommonUtil.getZeroHour();
			while (System.currentTimeMillis() - lastUpdateSendCountTime > CommonConst.Time.MILLIS_OF_ONE_DAY) {
				int sentMailToday = SENT_MAIL_TODAY.get();
				lastUpdateSendCountTime = LAST_UPDATE_SEND_COUNT_TIME.get();
				SENT_MAIL_TODAY.compareAndSet(sentMailToday, 0);
				LAST_UPDATE_SEND_COUNT_TIME.compareAndSet(lastUpdateSendCountTime, newUpdateSendCountTime);
				lastUpdateSendCountTime = LAST_UPDATE_SEND_COUNT_TIME.get();
				System.out.println(LAST_UPDATE_SEND_COUNT_TIME);
			}

			// 检查当天发送邮件数量，如果已经达到上限则返回错误
			if (SENT_MAIL_TODAY.get() == CommonConst.Mail.MAX_MAIL_SEND_ONE_DAY) {
				return ErrorCodeEnum.MAX_MAIL_SENT_TODAY.getResult();
			}
			SENT_MAIL_TODAY.incrementAndGet();
		}

		// 生成随机long类型数值
		long randomAuthCode = RANDOM.nextLong();
		String authCode = String.valueOf(randomAuthCode);

		// 获取六位数验证码
		authCode = "your auth code is:" + authCode.substring(1, 7) + ". Please use it in one minute!";

		// 封装Mail 对象
		String mailUuid = UUID.randomUUID().toString();
		System.out.println("creat a mail for sending: " + mailUuid);
		Mail mail = new Mail(recipient, "Distrident Mail Auth Code", authCode, mailUuid, new Date());
		return sendMail(mail);
	}

    /**
     * 主要是要等待消息队列中当前进程要发送的邮件发送后获得反馈。所以才需要加上同步代码块等待和唤醒
     * @param mail Mail 封装类型
     * @return 返回发送邮件的状态
     */
	public synchronized Result sendMail(Mail mail) {
        Result result;
        try {
			MAIL_QUEUE.put(mail);

			// 给当前邮件生成一个uuid
			String mailUuid = mail.getMailUuid();

            mail = MAIL_QUEUE.peek();
            while (true) {
				assert mail != null;
				// 判断当前消息队列队头的邮件的uuid是否与当前进程要发送的uuid相同，不同则等待等待下个消息队列队头匹配，相同则退出循环
				if (!mail.getMailUuid().equals(mailUuid)) {
					wait();
					mail = MAIL_QUEUE.peek();
				} else {
					break;
				}
			}

            // 取出匹配的邮件，然后进行发送
			mail = MAIL_QUEUE.poll();
			assert mail != null;
			System.out.println("mail is sent: " + mail.toString());
			try {
			    // 发送操作
				result = doSend(mail);
			} catch (MessagingException e) {
				log.error(e.toString());
				return ErrorCodeEnum.MIME_MESSAGE_CREATE_ERROR.getResult();
			}
			// 等待三秒，防止短时间内发送多条邮件
			Thread.sleep(3000);
            // 唤醒其他等待匹配的进程
			notifyAll();

		} catch (InterruptedException e) {
			// TODO kafka
			log.error(e.toString());
			return ErrorCodeEnum.INTERRUPTED_EXCEPTION.getResult();
		}

		return result;
	}

	public Result doSend(Mail mail) throws MessagingException {
		MimeMessage message;
		try {
			message = createMessage(mail);
			Transport transport;
			transport = session.getTransport();
			transport.connect(CommonConst.Mail.SENDER_ACCOUNT, CommonConst.Mail.SENDER_PASSWORD);
			transport.sendMessage(message, message.getAllRecipients());
			transport.close();

			// 发送成功后在redis记下验证码
			redisTemplate.opsForValue().set(CommonConst.Redis.AUTH_CODE_PREFIX + mail.getRecipient(), mail.getAuthCode(), CommonConst.Time.SECONDS_OF_ONE_MIN, TimeUnit.SECONDS);


		} catch (MessagingException e) {
			throw new MessagingException(e.getMessage(), e.getNextException());
		}
		return Result.getSuccess();
	}

	private MimeMessage createMessage(Mail mail) throws MessagingException {
		MimeMessage message = new MimeMessage(session);
		try {
			// 设置内容
			message.setContent(CommonConst.Mail.CONTENT_PREFIX + mail.getAuthCode() + CommonConst.Mail.CONTENT_SUFFIX, "text/html;charset=UTF-8");
			// 设置主题
			message.setSubject(mail.getSubject(), "UTF-8");
			// 设置发件人
			message.setFrom(new InternetAddress(CommonConst.Mail.SENDER_MAIL_ADDRESS, CommonConst.Mail.SENDER_ACCOUNT));
			// 设置收件人
			message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(mail.getRecipient(), "Respected Distrident users"));
			message.saveChanges();
		} catch (MessagingException e) {
			throw new MessagingException(e.getMessage(), e.getNextException());
		} catch (UnsupportedEncodingException e) {
			throw new UnsupportedOperationException(e.getMessage(), e.getCause());
		}

		return message;
	}


}
